package org.fatewa.engine.advice;

import core.Data;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.fatewa.engine.advice.annotations.Mount;
import org.fatewa.engine.advice.annotations.OriginalResponse;
import org.fatewa.engine.advice.annotations.Tiled;
import org.fatewa.engine.advice.exception.RayquazaException;
import org.fatewa.engine.advice.protocol.RayquazaResponse;
import org.fatewa.engine.design.advice.AbstractExceptionProcessor;
import org.fatewa.engine.design.advice.AbstractOriginalProvider;
import org.fatewa.engine.design.advice.AbstractProtocolMapper;
import org.fatewa.engine.property.RayquazaResponseProperties;
import org.fatewa.engine.property.ResponseCodeMapper;
import org.fatewa.engine.property.ResponseMessageMapper;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author 4everlynn
 */
@SuppressWarnings({"NullableProblems"})
@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class RayquazaAdvice implements ResponseBodyAdvice<Object> {
    private final ResponseCodeMapper codeMapper;
    private final ResponseMessageMapper messageMapper;
    private final RayquazaResponseProperties properties;
    private final JacksonProperties jacksonProperties;
    private final ApplicationContext context;

    /**
     * 缓存控制器中类型的处理器，返回前处理属于每个请求的生命周期内的事务
     * 如果每次请求都需要进行反射则会有效率问题
     * 使用这样一个 Map 可以简单高效地提高请求结果处理效率，让反射操作仅在第一次执行
     */
    private static final Map<Class<?>, AbstractProtocolMapper<?>> CACHED_MAPPER = new ConcurrentHashMap<>();
    /**
     * 作用同上，但是针对异常处理
     */
    private static final Map<Class<?>, AbstractExceptionProcessor<? extends Exception>> CACHED_ERROR_PROCESSORS = new ConcurrentHashMap<>();

    /**
     * 维护不需要进行日志输出的异常类型
     * 目前主要是 {@link MethodArgumentTypeMismatchException}
     */
    private static final Set<Class<? extends Exception>> WHITE_EXCEPTION_SET = new CopyOnWriteArraySet<>();


    static {
        // 增加无需输出的异常类型
        WHITE_EXCEPTION_SET.add(MethodArgumentTypeMismatchException.class);
    }

    /**
     * 异常消息的最大长度
     */
    private static final int MAX_ERROR_LEN = 64;

    /**
     * 在本类注册到容器前，解析用户自定义异常处理器
     * 加快后续异常处理
     */
    @PostConstruct
    public void injectExceptionProcessor() {
        // 从 Spring 上下文中取出用户配置的所有异常
        //noinspection rawtypes
        Map<String, AbstractExceptionProcessor> processors = context.getBeansOfType(AbstractExceptionProcessor.class);
        for (String key : processors.keySet()) {
            AbstractExceptionProcessor<?> processor = processors.get(key);
            Type type = processor.getClass().getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] arguments = parameterizedType.getActualTypeArguments();
                // AbstractExceptionProcessor 仅有一个泛型, 类型必须为 Exception 子类
                if (arguments.length == 1 && Exception.class.isAssignableFrom((Class<?>) arguments[0])) {
                    // 添加至缓存处理器
                    CACHED_ERROR_PROCESSORS.put((Class<?>) arguments[0], processor);
                }
            }
        }
    }

    /**
     * 将异常栈转为一维集合
     *
     * @param e
     * @return 一维异常集合
     */
    private Set<Throwable> flatCauses(Exception e) {
        Set<Throwable> causes = new HashSet<>();
        // 临时接收变量
        Throwable t = e;
        while ((t = t.getCause()) != null) {
            causes.add(t);
        }
        return causes;
    }


    /**
     * 异常处理总入口
     *
     * @param e 异常
     * @return ResponseEntity
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Data> exceptionHandler(Exception e) {

        // 输出原始日志
        log.error("", e);

        // 准备一个 RayquazaResponse 用作返回
        RayquazaResponse response;

        // 从异常缓存器中尝试取出处理器
        //noinspection unchecked
        AbstractExceptionProcessor<Exception> processor =
                (AbstractExceptionProcessor<Exception>) CACHED_ERROR_PROCESSORS.get(e.getClass());

        // 处理器存在时，使用自定义处理器映射, 仅处理至第一个被匹配到的异常
        if (null != processor) {
            response = processor.map(e);
            // 该异常默认处理为 500
            return ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(response);
        }

        String message = e.getMessage();

        response = new RayquazaResponse()
                .errors(e)
                .code(HttpStatus.INTERNAL_SERVER_ERROR)
                .msg(messageMapper.getServerError());

        // 使用內部的 RayquazaException 抛出时，支持调整 Http Status
        if (e instanceof RayquazaException) {
            response.code(((RayquazaException) e).status());
        }

        // 小于 32 个中文汉字时，直接使用异常消息
        if (Strings.isNotEmpty(message) && message.length() <= MAX_ERROR_LEN) {
            response.msg(message);
        }

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(response);
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        String uri = ((ServletServerHttpRequest) request).getServletRequest().getRequestURI();
        if (this.whitelist(uri)) {
            return body;
        }
        // 检查映射器
        injectResponseMapper();
        final HttpServletResponse http = ((ServletServerHttpResponse) response).getServletResponse();

        RayquazaResponse rayquazaResponse = new RayquazaResponse();

        if (null == body) {
            return rayquazaResponse;
        }

        Class<?> bodyClass = body.getClass();

        // 获取到被调用的方法
        Method method = returnType.getMethod();
        // 方法标记了该注解，则 Rayquaza 将不参与返回结果处理
        final OriginalResponse originalResponse = Objects.requireNonNull(
                method).getAnnotation(OriginalResponse.class);

        // 原路返回
        if (null != originalResponse) {
            return body;
        }

        // 返回了协议，直接返回原始对象
        if (RayquazaResponse.class.isAssignableFrom(bodyClass)) {
            // 构建RayquazaResponse
            return buildRayquazaResponse((RayquazaResponse) body, http);
        } else if (Map.class.isAssignableFrom(bodyClass)) {
            //  Spring 内部的处理机制处理
            return this.handleDefault(body);
        }

        if (null != context) {

            // 优先尝试从缓存获取协议转换器
            //noinspection rawtypes
            AbstractProtocolMapper targetMapper = CACHED_MAPPER.get(bodyClass);

            if (null != targetMapper) {
                // 匹配成功，从缓存取出实例
                //noinspection unchecked
                return targetMapper.map(body, method, properties, jacksonProperties);
            }

            // 尝试获取当前类型的协议转换器
            //noinspection rawtypes
            Map<String, AbstractProtocolMapper> mapper = context.getBeansOfType(AbstractProtocolMapper.class);
            Assert.notNull(mapper, "protocol mapper not found");

            for (String key : mapper.keySet()) {
                //noinspection unchecked
                AbstractProtocolMapper<Object> protocolMapper = mapper.get(key);

                final Type superclass = protocolMapper.getClass().getGenericSuperclass();
                final Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments();

                // AbstractProtocolMapper 的入参范型数量为 1 个
                if (actualTypeArguments.length == 1) {
                    Type type = actualTypeArguments[0];
                    if (type instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) type;
                        if (((Class<?>) parameterizedType.getRawType()).isAssignableFrom(bodyClass)) {
                            // 缓存映射处理器为第二次请求加速
                            CACHED_MAPPER.put(bodyClass, protocolMapper);
                            // 使用自定义处理器
                            return protocolMapper.map(body, method, properties, jacksonProperties);
                        }
                    } else if (((Class<?>) type).isAssignableFrom(bodyClass)) {
                        // 缓存映射处理器为第二次请求加速
                        CACHED_MAPPER.put(bodyClass, protocolMapper);
                        // 使用自定义处理器
                        return protocolMapper.map(body, method, properties, jacksonProperties);
                    }
                }

            }
        }

        // 平铺对象的所有 key
        if (null != method.getAnnotation(Tiled.class)) {
            return rayquazaResponse.parties(body);
        }

        // 默认放置在 data 下
        String key = properties.getDataKey();

        // 获取用户是否指定了对应的 Key
        Mount mount = method.getAnnotation(Mount.class);

        // 使用用户自定义 Key
        if (null != mount) {
            key = mount.value();
        }

        // 默认内容放置在 key 下方
        return rayquazaResponse
                .include(key, body);
    }

    /**
     * 判断当前请求的路径是否为白名单内(即，不使用 rayquaza 进行 response 处理)
     *
     * @param uri 请求的路径
     * @return 是否为白名单内
     */
    private boolean whitelist(String uri) {
        Map<String, AbstractOriginalProvider> providers = context.getBeansOfType(AbstractOriginalProvider.class);
        for (AbstractOriginalProvider provider : providers.values()) {
            provider.configure();
            if (provider.match(uri)) {
                log.info("URI: {} whitelist matched", uri);
                return true;
            }
        }
        return false;
    }


    /**
     * 构建 {@link RayquazaResponse} 并做前置处理
     *
     * @param body 当前返回的 body
     * @param http HttpServletResponse 对象
     * @return 构建完毕的 {@link RayquazaResponse}
     */
    private RayquazaResponse buildRayquazaResponse(RayquazaResponse body, HttpServletResponse http) {
        // 异常栈在返回前去除
        body.remove(RayquazaResponse.KEY_ERRORS);
        // 将协议对象的状态码设置为协议的状态码
        Integer status = body.code();
        if (null != status) {
            http.setStatus(status);
        }
        // 直接返回协议本身
        return body;
    }

    private void injectResponseMapper() {
        if (null == properties.getCodeMapper()) {
            properties.setCodeMapper(codeMapper);
        }

        if (null == properties.getMessageMapper()) {
            properties.setMessageMapper(messageMapper);
        }
    }

    /**
     * 处理 Spring 内部的请求处理结果
     *
     * @param body 内部的请求处理结果
     * @return RayquazaResponse
     */
    private Object handleDefault(Object body) {

        if (body instanceof Map) {
            RayquazaResponse rayquazaResponse = new RayquazaResponse();
            Map<?, ?> map = (Map<?, ?>) body;
            // Spring 内部的 Map 状态 Key
            String status = "status";
            // Spring 内部的 Map 请求路径 Key
            String path = "path";

            if (map.containsKey(status) && map.containsKey(path)) {
                rayquazaResponse.parties(map);
                rayquazaResponse
                        .code(rayquazaResponse.getIntegerPart(status))
                        .msg(rayquazaResponse.getStringPart("error"))
                        .exclude(status)
                        .exclude("error")
                        .retain("code", "message");
            } else {
                rayquazaResponse
                        .code(codeMapper.getSuccess())
                        .msg(messageMapper.getSuccess())
                        .include(properties.getDataKey(), body);
            }
            return rayquazaResponse;
        }

        return body;
    }
}
